// Author: wsfuyibing <682805@qq.com>
// Date: 2025-06-24

package operations

import (
	"context"
	"gitee.com/go-libs/db-xorm/db"
	"gitee.com/go-libs/log"
	"server/app/errs"
	"server/app/http/models"
	"server/app/http/services"
	"server/app/kind"
	"server/app/lib/user_balance"
)

// Operations
// 订单公共方法.
type Operations struct{}

func (o *Operations) CheckGoodsEntity(ctx context.Context, entity *GoodsEntity) (pre *Prepay, err error) {
	pre = NewPrepay()

	// 1. 基础校验.
	//    a. 元素库存
	//    b. 会员抵扣.
	err = o.checkGoodsItemList(ctx, pre, entity)

	// 2. 校验优惠.
	if entity.Coupon != nil && entity.Coupon.Available() && entity.Coupon.Type.IsGoods() {
		err = o.checkDiscountWithCoupon(ctx, pre, entity.Coupon)
	}

	// 3. 余额抵扣.
	if err == nil && entity.UseUserBalance {
		err = o.checkDiscountWithBalance(ctx, pre, &entity.Entity)
	}
	return
}

func (o *Operations) CheckSessionEntity(ctx context.Context, entity *SessionEntity) (pre *Prepay, err error) {
	pre = NewPrepay()

	// 1. 基础校验.
	//    a. 元素库存
	//    b. 会员抵扣.
	if err = o.checkSessionItemList(ctx, pre, entity); err != nil {
		return
	}

	// 2. 校验优惠.
	if entity.Coupon != nil && entity.Coupon.Available() && entity.Coupon.Type.IsSession() {
		err = o.checkDiscountWithCoupon(ctx, pre, entity.Coupon)
	}

	// 3. 余额抵扣.
	if err == nil && entity.UseUserBalance {
		err = o.checkDiscountWithBalance(ctx, pre, &entity.Entity)
	}
	return
}

func (o *Operations) Create(ctx context.Context, sess *db.Session, entity *Entity, prepay *Prepay) (order *models.Order, err error) {
	// 1. 开始写入.
	log.Infofc(ctx, `[lib][order] 开始写入数据`)

	// 2. 监听结束.
	defer func() {
		if err != nil {
			log.Warnfc(ctx, `[lib][order] 写入数据失败: %v`, err)
		} else {
			log.Infofc(ctx, `[lib][order] 写入数据结束`)
		}
	}()

	// 3. 串行写入.
	if order, err = o.createOrder(ctx, sess, prepay, entity); err == nil {
		if err = o.createOrderItem(ctx, sess, order, entity); err == nil {
			if err = o.createOrderPayKind(ctx, sess, order, prepay, entity); err == nil {
				if err = o.createOrderArcade(ctx, sess, order, entity); err == nil {
					if err = o.createOrderUser(ctx, sess, order, entity); err == nil {
						err = o.createOrderLog(ctx, sess, order)
					}
				}
			}
		}
	}
	return
}

func (o *Operations) GetArcadeById(ctx context.Context, sess *db.Session, aid kind.ArcadeId) (model *models.Arcades, err error) {
	var has bool
	if model, has, err = services.NewArcadesService(sess).Dao.GetById(ctx, aid); err != nil {
		err = errs.ErrDatabaseQuery
		return
	}
	if !has {
		err = errs.ErrArcadeNotFound
	}
	return
}

func (o *Operations) GetArcadeMemberByIds(ctx context.Context, sess *db.Session, aid kind.ArcadeId, uid kind.UserId) (model *models.ArcadeMember, err error) {
	if model, _, err = services.NewArcadeMemberService(sess).Dao.GetByMap(ctx, map[string]any{
		"arcade_id": aid,
		"user_id":   uid,
	}); err != nil {
		err = errs.ErrDatabaseQuery
	}
	return
}

func (o *Operations) GetArcadeSettingById(ctx context.Context, sess *db.Session, aid kind.ArcadeId) (model *models.ArcadeSetting, err error) {
	var has bool
	if model, has, err = services.NewArcadeSettingService(sess).Dao.GetByMap(ctx, map[string]any{
		"arcade_id": aid,
	}); err != nil {
		err = errs.ErrDatabaseQuery
		return
	}
	if !has {
		err = errs.ErrArcadeSettingNotFound
	}
	return
}

func (o *Operations) GetCouponById(ctx context.Context, sess *db.Session, id kind.CouponId) (model *models.Coupon, err error) {
	var has bool
	if model, has, err = services.NewCouponService(sess).Dao.GetById(ctx, id); err != nil {
		err = errs.ErrDatabaseQuery
		return
	}
	if !has {
		err = errs.ErrCouponNotFound
	}
	return
}

func (o *Operations) GetOrderItemListById(ctx context.Context, sess *db.Session, oid int64) (list []*models.OrderItem, err error) {
	if list, err = services.NewOrderItemService(sess).Dao.ListByMap(ctx, map[string]any{
		"order_id": oid,
	}); err != nil {
		err = errs.ErrDatabaseQuery
	}
	return
}

func (o *Operations) GetOrderPayKindListById(ctx context.Context, sess *db.Session, oid int64) (list []*models.OrderPayKind, err error) {
	if list, err = services.NewOrderPayKindService(sess).Dao.ListByMap(ctx, map[string]any{
		"order_id": oid,
	}); err != nil {
		err = errs.ErrDatabaseQuery
	}
	return
}

func (o *Operations) GetSessionById(ctx context.Context, sess *db.Session, id int64) (model *models.TicketSession, err error) {
	var has bool
	if model, has, err = services.NewTicketSessionService(sess).Dao.GetById(ctx, id); err != nil {
		err = errs.ErrDatabaseQuery
		return
	}
	if !has || model.Deleted.IsYes() {
		err = errs.ErrTicketSessionNotFound
	}
	return
}

func (o *Operations) GetTicketById(ctx context.Context, sess *db.Session, id int64) (model *models.Ticket, err error) {
	var has bool
	if model, has, err = services.NewTicketService(sess).Dao.GetById(ctx, id); err != nil {
		err = errs.ErrDatabaseQuery
		return
	}
	if !has || model.Deleted.IsYes() {
		err = errs.ErrTicketNotFound
	}
	return
}

func (o *Operations) GetUserById(ctx context.Context, sess *db.Session, id kind.UserId) (model *models.Users, err error) {
	var has bool
	if model, has, err = services.NewUsersService(sess).Dao.GetById(ctx, id); err != nil {
		err = errs.ErrDatabaseQuery
		return
	}
	if !has {
		err = errs.ErrUserNotFound
		return
	}
	if !model.State.IsNormal() {
		err = errs.ErrUserRestricted
	}
	return
}

func (o *Operations) GetUserStatisticById(ctx context.Context, sess *db.Session, id kind.UserId) (model *models.UserStatistic, err error) {
	var has bool
	if model, has, err = services.NewUserStatisticService(sess).Dao.GetByMap(ctx, map[string]any{
		"user_id": id,
	}); err != nil {
		err = errs.ErrDatabaseQuery
		return
	}
	if !has {
		err = errs.ErrUserStatisticNotFound
	}
	return
}

func (o *Operations) LoadEntityForGoods(ctx context.Context, sess *db.Session, entity *GoodsEntity) (err error) {
	var (
		ids    = make([]kind.GoodsId, 0)
		mapper map[kind.GoodsId]*models.Goods
	)

	// 1. 遍历入参.
	for k := range entity.Carts {
		if k.IsBound() {
			ids = append(ids, k)
		}
	}

	// 2. 读取商品.
	if len(ids) > 0 {
		// 2.1 商品列表.
		if mapper, err = services.NewGoodsService(sess).MapByIdList(ctx, ids); err != nil {
			return
		}

		// 2.2 遍历商品.
		for k, g := range mapper {
			if q, ok := entity.Carts[k]; ok {
				entity.Entity.AddItem(g, q, false)
			}
		}
	}

	// 3. 公共加载.
	err = o.loadEntity(ctx, sess, &entity.Entity)
	return
}

func (o *Operations) LoadEntityForSession(ctx context.Context, sess *db.Session, entity *SessionEntity) (err error) {
	// 1. 读取渔票.
	//    a. 场次信息
	//    b. 渔票信息
	//    c. 购入清单
	if entity.Session, err = o.GetSessionById(ctx, sess, entity.SessionId); err == nil {
		if entity.Ticket, err = o.GetTicketById(ctx, sess, entity.Session.TicketId); err == nil {
			if entity.Ticket.ArcadeId != entity.ArcadeId {
				err = errs.ErrTicketSessionNotFound
			} else {
				entity.AddSessionItem()
				err = o.loadSessionGoods(ctx, sess, entity)
			}
		}
	}

	// 2. 公共加载.
	if err == nil {
		err = o.loadEntity(ctx, sess, &entity.Entity)
	}
	return
}

// Lock
// 冻结订单数据.
func (o *Operations) Lock(ctx context.Context, sess *db.Session, order *models.Order) (err error) {
	// 1. 开始冻结.
	log.Infofc(ctx, `[lib][order][lock] 开始冻结`)

	// 2. 监听结束.
	defer func() {
		if err != nil {
			log.Warnfc(ctx, `[lib][order][lock] 冻结失败: %v`, err)
		} else {
			log.Infofc(ctx, `[lib][order][lock] 冻结结束`)
		}
	}()

	// 3. 冻结过程.
	if err = o.lockItem(ctx, sess, order); err == nil {
		err = o.lockPayKind(ctx, sess, order)
	}
	return
}

// Success
// 支付完成.
func (o *Operations) Success(ctx context.Context, sess *db.Session, order *models.Order) (err error) {
	return
}

// Unlock
// 解冻订单数据.
func (o *Operations) Unlock(ctx context.Context, sess *db.Session, order *models.Order) (err error) {
	// 1. 开始解冻.
	log.Infofc(ctx, `[lib][order][unlock] 开始解冻`)

	// 2. 监听结束.
	defer func() {
		if err != nil {
			log.Warnfc(ctx, `[lib][order][unlock] 解冻失败: %v`, err)
		} else {
			log.Infofc(ctx, `[lib][order][unlock] 解冻结束`)
		}
	}()

	// 3. 解冻过程.
	if err = o.unlockItem(ctx, sess, order); err == nil {
		err = o.unlockPayKind(ctx, sess, order)
	}
	return
}

func (o *Operations) UpdateStateAsClosed(ctx context.Context, sess *db.Session, order *models.Order, op kind.OrderOperation, by kind.ClosedBy, link kind.Link, params kind.LinkParams) (err error) {
	return o.updateState(ctx, sess, order, kind.OrderStateClosed, op, by, link, params)
}

// +---------------------------------------------------------------------------+
// | Access to check goods.                                                    |
// +---------------------------------------------------------------------------+

func (o *Operations) checkDiscountWithBalance(ctx context.Context, pre *Prepay, entity *Entity) (err error) {
	var dis kind.Balance

	// 1. 开始校验.
	log.Infofc(ctx, `[lib][order][check][balance] 开始校验: 买家ID="%v", 买家名="%s", 买家余额="%v"`,
		entity.User.Id,
		entity.User.Nickname,
		entity.UserStatistic.Balance,
	)

	// 2. 监听结束.
	defer func() {
		if err != nil {
			log.Warnfc(ctx, `[lib][order][check][balance] 校验失败: %v`, err)
		} else {
			log.Infofc(ctx, `[lib][order][check][balance] 校验结束: 抵扣="%v"`, dis)
		}
	}()

	// 3. 全额抵扣.
	if entity.UserStatistic.Balance.IsPositive() {
		dis = entity.UserStatistic.Balance

		// 3.1 抵扣上限.
		//     抵扣金额不能超过自付金额.
		if pay := pre.ResetPayAmount().PayAmount; dis.Greater(pay) {
			dis = pay
		}

		// 3.2 设置抵扣.
		pre.BalanceAmount = dis
	}
	return
}

func (o *Operations) checkDiscountWithCoupon(ctx context.Context, pre *Prepay, coupon *models.Coupon) (err error) {
	var (
		dis kind.Balance
		typ = coupon.DiscountType
		val = coupon.DiscountValue
	)

	// 1. 开始校验.
	log.Infofc(ctx, `[lib][order][check][coupon] 开始校验: 优惠券ID="%v", 优惠券名="%s", 优惠模式="%v", 优惠参数="%v", 优惠上限="%v"`,
		coupon.Id, coupon.Name,
		coupon.DiscountType.Text(), coupon.DiscountValue, coupon.DiscountMax,
	)

	// 2. 监听结束.
	defer func() {
		if err != nil {
			log.Warnfc(ctx, `[lib][order][check][coupon] 校验失败: %v`, err)
		} else {
			log.Infofc(ctx, `[lib][order][check][coupon] 校验结束: 优惠="%v"`, dis)
		}
	}()

	// 3. 计算优惠.
	if typ.IsFixed() {
		// 3.1 固定减免.
		dis = coupon.DiscountValue
	} else if typ.IsPercent() {
		// 3.2 比例减免.
		if per := int(val * 100); per >= 10 && per <= 100 {
			dis = kind.Balance((1.0 - coupon.DiscountValue.Float()) * pre.TotalAmount.Float())
		} else {
			log.Warnfc(ctx, `[lib][order][check][coupon] 无效优惠券: 错误="无效参数", 参数="%v", 提示="折扣比例必须在0.1到1.0之间"`, val)
		}
	}

	// 4. 优惠上限.
	//    优惠金额不能超过约定的最大金额.
	if dis.Greater(coupon.DiscountMax) {
		dis = coupon.DiscountMax
	}

	// 5. 整体上限.
	if pay := pre.ResetPayAmount().PayAmount; dis.Greater(pay) {
		dis = pay
	}

	// 6. 设置结果.
	pre.CouponAmount = dis
	return
}

func (o *Operations) checkGoodsItem(ctx context.Context, pre *Prepay, entity *GoodsEntity, item *models.OrderItem) (err error) {
	var (
		goods *models.Goods
		ok    bool
	)

	// 1. 开始校验.
	log.Infofc(ctx, `[lib][order][check][goods] 开始校验: 商品ID="%v", 商品名="%s", 购入数量="%v", 购入单价="%v", 购入总价="%v"`,
		item.ResourceId, item.Name,
		item.Quantity, item.Price, item.Amount,
	)

	// 2. 监听结束.
	defer func() {
		if err != nil {
			log.Warnfc(ctx, `[lib][order][check][goods] 校验失败: %v`, err)
		} else {
			log.Infofc(ctx, `[lib][order][check][goods] 校验结束`)
		}
	}()

	// 3. 商品元素
	if goods, ok = entity.Goods[kind.GoodsId(item.ResourceId)]; !ok {
		err = errs.ErrGoodsNotFound
		return
	}

	// 4. 库存不足.
	if goods.Stock.Less(item.Quantity + goods.LockedCount + goods.SaleCount) {
		err = errs.ErrGoodsStockNotEnough
		return
	}

	// 5. 会员抵扣.
	//    钓场开启了低价购物时生效.
	if entity.ArcadeSetting.MemberGoodsEnabled.IsYes() {
		dis := kind.Balance(0)
		typ := entity.ArcadeSetting.MemberGoodsDiscountType
		val := entity.ArcadeSetting.MemberGoodsDiscountAmount

		// 5.1 模式选择.
		if typ.IsFixed() {
			// 5.1.1 固定金额.
			//       每张票固定减去指定金额.
			dis = kind.Balance(item.Quantity.Float() * val)
		} else if typ.IsPercent() {
			// 5.1.2 比例折扣.
			//       在总价上取折扣, 如0.85表示85%(即85折), 15%的部分被抵扣.
			if per := int(val * 100); per >= 10 && per <= 100 {
				dis = kind.Balance((1.0 - val) * item.Amount.Float())
			} else {
				log.Warnfc(ctx, `[lib][order][check][goods] 低价购物无效: 错误="无效参数", 参数="%v", 提示="折扣比例必须在0.1到1.0之间"`, val)
			}
		}

		// 5.2 最大抵扣.
		//     抵扣金额不能超过总价格.
		if dis.IsPositive() && dis.Greater(item.Amount) {
			dis = item.Amount
		}

		// 5.3 设置抵扣.
		pre.MemberCanAmount += dis

		// 4.4 实际抵扣.
		//     买家(钓友)已是卖家(钓场)的会员, 且正在服务期内.
		if entity.ArcadeMember != nil && entity.ArcadeMember.Available() {
			pre.MemberAmount += dis
			item.Discount = dis
			log.Infofc(ctx, `[lib][order][check][goods] 低价购物: 模式="%s", 参数="%v", 抵扣="%v"`, typ.Text(), val, dis)
		} else {
			log.Infofc(ctx, `[lib][order][check][goods] 低价购物: 忽略="非会员或已过期", 模式="%s", 参数="%v"`, typ.Text(), val)
		}
	} else {
		log.Infofc(ctx, `[lib][order][check][goods] 低价购物: 未开启`)
	}
	return
}

func (o *Operations) checkGoodsItemList(ctx context.Context, pre *Prepay, entity *GoodsEntity) (err error) {
	for _, item := range entity.Items {
		pre.TotalAmount += item.Amount
		if item.OrderKind.IsGoods() {
			err = o.checkGoodsItem(ctx, pre, entity, item)
		}
	}
	return
}

func (o *Operations) checkSessionGoods(ctx context.Context, entity *SessionEntity, item *models.OrderItem) (err error) {
	var (
		goods     *models.Goods
		ok        bool
		remaining kind.GoodsStock
	)

	// 1. 开始校验.
	log.Infofc(ctx, `[lib][order][check][goods] 开始校验: 商品ID="%v", 商品名="%s", 购入数量="%v", 购入单价="%v", 购入总价="%v"`,
		item.ResourceId, item.Name,
		item.Quantity, item.Price, item.Amount,
	)

	// 2. 监听结束.
	defer func() {
		if err != nil {
			log.Warnfc(ctx, `[lib][order][check][goods] 校验失败: %v`, err)
		} else {
			log.Infofc(ctx, `[lib][order][check][goods] 校验结束`)
		}
	}()

	// 3. 商品元素
	if goods, ok = entity.Goods[kind.GoodsId(item.ResourceId)]; !ok {
		err = errs.ErrGoodsNotFound
		return
	}

	// 4. 库存不足.
	if remaining = goods.Stock - goods.LockedCount - goods.SaleCount; remaining.LessEqual(kind.GoodsStockEmpty) {
		entity.RemoveItem(goods)
		log.Infofc(ctx, `[lib][order][check][goods] 移除赠品 - 库存不足`)
		return
	}

	// 5. 重置数量.
	//    例如按规则应赠出2件商品, 但库只剩余1件时, 赠品数量从2改为1.
	if item.Quantity.Greater(remaining) {
		item.Quantity = remaining
		log.Infofc(ctx, `[lib][order][check][goods] 重置数量 - 赠出最后剩余赠品`)
	}
	return
}

func (o *Operations) checkSessionItem(ctx context.Context, pre *Prepay, entity *SessionEntity, item *models.OrderItem) (err error) {
	// 1. 开始校验.
	log.Infofc(ctx, `[lib][order][check][session] 开始校验: 钓场ID="%v", 钓场名="%s", 场次ID="%v", 场次名="%v", 场次号="%v", 购入数量="%v", 购入单价="%v", 购入总价="%v"`,
		entity.Arcade.Id, entity.Arcade.Name,
		item.ResourceId, item.Name, entity.Session.Date,
		item.Quantity, item.Price, item.Amount,
	)

	// 2. 监听结束.
	defer func() {
		if err != nil {
			log.Warnfc(ctx, `[lib][order][check][session] 校验失败: %v`, err)
		} else {
			log.Infofc(ctx, `[lib][order][check][session] 校验结束`)
		}
	}()

	// 3. 库存检查.
	if kind.GoodsStock(entity.Ticket.Quantity).Less(kind.GoodsStock(entity.Session.OrderCount+entity.Session.LockedCount) + item.Quantity) {
		err = errs.ErrTicketSessionQuantityNotEnough
		return
	}

	// 4. 会员抵扣.
	//    钓场开启了低价购票时生效.
	if entity.ArcadeSetting.MemberTicketEnabled.IsYes() {
		dis := kind.Balance(0)
		typ := entity.ArcadeSetting.MemberTicketDiscountType
		val := entity.ArcadeSetting.MemberTicketDiscountAmount

		// 4.1 模式选择.
		if typ.IsFixed() {
			// 4.1.1 固定金额.
			//       每张票固定减去指定金额.
			dis = kind.Balance(item.Quantity.Float() * val)
		} else if typ.IsPercent() {
			// 4.1.2 比例折扣.
			//       在总价上取折扣, 如0.85表示85%(即85折), 15%的部分被抵扣.
			if per := int(val * 100); per >= 10 && per <= 100 {
				dis = kind.Balance((1.0 - val) * item.Amount.Float())
			} else {
				log.Warnfc(ctx, `[lib][order][check][session] 低价购票无效: 错误="无效参数", 参数="%v", 提示="折扣比例必须在0.1到1.0之间"`, val)
			}
		}

		// 4.2 最大抵扣.
		//     抵扣金额不能超过总价格.
		if dis.IsPositive() && dis.Greater(item.Amount) {
			dis = item.Amount
		}

		// 4.3 设置抵扣.
		pre.MemberCanAmount = dis

		// 4.4 实际抵扣.
		//     买家(钓友)已是卖家(钓场)的会员, 且正在服务期内.
		if entity.ArcadeMember != nil && entity.ArcadeMember.Available() {
			pre.MemberAmount = pre.MemberCanAmount
			item.Discount = dis
			log.Infofc(ctx, `[lib][order][check][session] 低价购票: 模式="%s", 参数="%v", 抵扣="%v"`, typ.Text(), val, dis)
		} else {
			log.Infofc(ctx, `[lib][order][check][session] 低价购票: 忽略="非会员或已过期", 模式="%s", 参数="%v"`, typ.Text(), val)
		}
	} else {
		log.Infofc(ctx, `[lib][order][check][session] 低价购票: 未开启`)
	}
	return
}

func (o *Operations) checkSessionItemList(ctx context.Context, pre *Prepay, entity *SessionEntity) (err error) {
	// 1. 遍历条目.
	for _, item := range entity.Items {
		// 2. 总价累加.
		pre.TotalAmount += item.Amount

		// 3. 分类校验.
		if item.OrderKind.IsSession() {
			// 3.1 校验场次.
			err = o.checkSessionItem(ctx, pre, entity, item)
		} else if item.OrderKind.IsGoods() {
			// 3.2 校验商品.
			err = o.checkSessionGoods(ctx, entity, item)
		}

		// 4. 校验错误.
		if err != nil {
			break
		}
	}
	return
}

// +---------------------------------------------------------------------------+
// | Access to create model for order                                          |
// +---------------------------------------------------------------------------+

func (o *Operations) createOrder(ctx context.Context, sess *db.Session, prepay *Prepay, entity *Entity) (order *models.Order, err error) {
	var (
		images = make([]string, 0)
		ono    = kind.NewOrderNo(ctx)
	)

	// 1. 条目图片.
	for _, item := range entity.Items {
		if item.Image != "" {
			images = append(images, item.Image)
		}
	}

	// 2. 创建记录.
	if order, err = services.NewOrderService(sess).Dao.AddByStruct(ctx, &models.Order{
		OrderNo:     ono,
		OrderKind:   entity.Kind,
		ArcadeId:    entity.Arcade.Id,
		UserId:      entity.User.Id,
		State:       kind.OrderStateNot,
		RefundState: kind.RefundStateNot,
		TotalAmount: prepay.TotalAmount,
		PayAmount:   prepay.PayAmount,
		ClosedBy:    kind.ClosedByNot,
		Images:      images,
	}); err != nil {
		err = errs.ErrDatabaseQuery
	} else {
		log.Infofc(ctx, `[lib][order][create] 订单创建完成: 订单ID="%v", 订单号="%s"`, order.Id, order.OrderNo)
	}
	return
}

func (o *Operations) createOrderItem(ctx context.Context, sess *db.Session, order *models.Order, entity *Entity) (err error) {
	var (
		item    *models.OrderItem
		service = services.NewOrderItemService(sess)
	)

	// 1. 遍历条目.
	for _, v := range entity.Items {
		if item, err = service.Dao.AddByStruct(ctx, &models.OrderItem{
			OrderId:    order.Id,
			OrderKind:  v.OrderKind,
			Locked:     kind.LockedStateNot,
			Amount:     v.Amount,
			Discount:   v.Discount,
			Price:      v.Price,
			Quantity:   v.Quantity,
			ResourceId: v.ResourceId,
			Unit:       v.Unit,
			Name:       v.Name,
			Image:      v.Image,
		}); err != nil {
			err = errs.ErrDatabaseQuery
			break
		} else {
			log.Infofc(ctx, `[lib][order][create] 条目创建完成: 条目ID="%v", 条目名称="%s", 条目金额="%v", 抵扣金额="%v"`, item.Id, item.Name, item.Amount, item.Discount)
		}
	}
	return
}

func (o *Operations) createOrderPayKind(ctx context.Context, sess *db.Session, order *models.Order, prepay *Prepay, entity *Entity) (err error) {
	var (
		pay     *models.OrderPayKind
		pays    = make([]*models.OrderPayKind, 0)
		service = services.NewOrderPayKindService(sess)
	)

	// 1. 会员抵扣.
	if prepay.MemberAmount.IsPositive() {
		pays = append(pays, &models.OrderPayKind{
			OrderId:    order.Id,
			PayKind:    kind.PayKindMember,
			Locked:     kind.LockedStateNot,
			Amount:     prepay.MemberAmount,
			ResourceId: entity.ArcadeMember.Id,
		})
	}

	// 2. 优惠券抵扣.
	if prepay.CouponAmount.IsPositive() {
		pays = append(pays, &models.OrderPayKind{
			OrderId:    order.Id,
			PayKind:    kind.PayKindCoupon,
			Locked:     kind.LockedStateNot,
			Amount:     prepay.CouponAmount,
			ResourceId: entity.Coupon.Id.Int64(),
		})
	}

	// 3. 余额抵扣.
	if prepay.BalanceAmount.IsPositive() {
		pays = append(pays, &models.OrderPayKind{
			OrderId:    order.Id,
			PayKind:    kind.PayKindUserBalance,
			Locked:     kind.LockedStateNot,
			Amount:     prepay.BalanceAmount,
			ResourceId: entity.User.Id.Int64(),
		})
	}

	// 4. 用户自付.
	if prepay.PayAmount.IsPositive() {
		pays = append(pays, &models.OrderPayKind{
			OrderId:    order.Id,
			PayKind:    kind.PayKindUser,
			Locked:     kind.LockedStateNot,
			Amount:     prepay.PayAmount,
			ResourceId: entity.User.Id.Int64(),
		})
	}

	// 5. 写入支付.
	for _, v := range pays {
		if pay, err = service.Dao.AddByStruct(ctx, v); err != nil {
			err = errs.ErrDatabaseQuery
			break
		} else {
			log.Infofc(ctx, `[lib][order][create] 支付创建完成: 支付ID="%v", 支付类型="%s", 支付金额="%v"`, pay.Id, pay.PayKind.Text(), pay.Amount)
		}
	}
	return
}

func (o *Operations) createOrderArcade(ctx context.Context, sess *db.Session, order *models.Order, entity *Entity) (err error) {
	var (
		model   *models.OrderArcade
		service = services.NewOrderArcadeService(sess)
	)

	// 1. 添加记录.
	if model, err = service.Dao.AddByStruct(ctx, &models.OrderArcade{
		OrderId:  order.Id,
		ArcadeId: entity.Arcade.Id,
		State:    order.State,
	}); err != nil {
		err = errs.ErrDatabaseQuery
	} else {
		log.Infofc(ctx, `[lib][order][create] 钓场关系数据: ID="%v", 钓场ID="%v", 钓场名="%s"`, model.Id, entity.Arcade.Id, entity.Arcade.Name)
	}

	// 2. 删除状态.
	if _, err = service.Dao.UpdateFieldsById(ctx, map[string]any{
		"deleted": kind.DeletedNo,
	}, model.Id); err != nil {
		err = errs.ErrDatabaseQuery
	}
	return
}

func (o *Operations) createOrderUser(ctx context.Context, sess *db.Session, order *models.Order, entity *Entity) (err error) {
	var (
		model   *models.OrderUser
		service = services.NewOrderUserService(sess)
	)

	// 1. 添加记录.
	if model, err = service.Dao.AddByStruct(ctx, &models.OrderUser{
		OrderId: order.Id,
		UserId:  entity.UserId,
		State:   order.State,
	}); err != nil {
		err = errs.ErrDatabaseQuery
	} else {
		log.Infofc(ctx, `[lib][order][create] 钓友关系数据: ID="%v", 钓友ID="%v", 钓友名="%s"`, model.Id, entity.User.Id, entity.User.Nickname)
	}

	// 2. 删除状态.
	if _, err = service.Dao.UpdateFieldsById(ctx, map[string]any{
		"deleted": kind.DeletedNo,
	}, model.Id); err != nil {
		err = errs.ErrDatabaseQuery
	}
	return
}

func (o *Operations) createOrderLog(ctx context.Context, sess *db.Session, order *models.Order) (err error) {
	if _, err = services.NewOrderLogService(sess).Dao.AddByStruct(ctx, &models.OrderLog{
		OrderId:    order.Id,
		Operation:  kind.OrderOperationCreate,
		Link:       kind.LinkDetailUser,
		LinkParams: map[string]any{"UserId": order.UserId},
		Message:    kind.OrderOperationCreate.ToOrderOperationCreate(order.OrderNo, order.TotalAmount),
	}); err != nil {
		err = errs.ErrDatabaseQuery
	} else {
		log.Infofc(ctx, `[lib][order][create] 创建操作日志`)
	}
	return
}

// +---------------------------------------------------------------------------+
// | Access to load model into entity                                          |
// +---------------------------------------------------------------------------+

func (o *Operations) loadEntity(ctx context.Context, sess *db.Session, entity *Entity) (err error) {
	// 1. 加载钓场.
	//    a. 钓场信息
	//    b. 钓场设置
	if entity.ArcadeId.IsBound() {
		if entity.Arcade, err = o.GetArcadeById(ctx, sess, entity.ArcadeId); err == nil {
			entity.ArcadeSetting, err = o.GetArcadeSettingById(ctx, sess, entity.Arcade.Id)
		}
	}

	// 2. 加载钓友.
	if err == nil && entity.UserId.IsBound() {
		if entity.User, err = o.GetUserById(ctx, sess, entity.UserId); err == nil {
			entity.UserStatistic, err = o.GetUserStatisticById(ctx, sess, entity.UserId)
		}
	}

	// 3. 钓场会员.
	if err == nil && entity.Arcade != nil && entity.User != nil {
		entity.ArcadeMember, err = o.GetArcadeMemberByIds(ctx, sess, entity.ArcadeId, entity.UserId)
	}

	// 4. 优惠券.
	if entity.CouponId.IsBound() {
		entity.Coupon, err = o.GetCouponById(ctx, sess, entity.CouponId)
	}
	return
}

func (o *Operations) loadSessionGoods(ctx context.Context, sess *db.Session, entity *SessionEntity) (err error) {
	var (
		free   []*models.TicketFreeGoods
		goods  map[kind.GoodsId]*models.Goods
		ids    = make([]kind.GoodsId, 0)
		mapper = make(map[kind.GoodsId]kind.GoodsStock)
	)

	// 1. 读取赠品.
	if free, err = services.NewTicketFreeGoodsService(sess).Dao.ListByMap(ctx, map[string]any{
		"session_id": entity.SessionId,
	}); err != nil {
		err = errs.ErrDatabaseQuery
		return
	}

	// 2. 遍历赠品.
	for _, v := range free {
		if v.GoodsId.IsBound() {
			ids = append(ids, v.GoodsId)
			mapper[v.GoodsId] = v.Quantity * entity.Quantity
		}
	}

	// 3. 读取商品.
	if goods, err = services.NewGoodsService(sess).MapByIdList(ctx, ids); err != nil {
		return
	}

	// 4. 构建商品.
	for _, k := range ids {
		if q, ok := mapper[k]; ok {
			if g, has := goods[k]; has {
				entity.Entity.AddItem(g, q, true)
			}
		}
	}
	return
}

// +---------------------------------------------------------------------------+
// | Access to lock & unlock model data                                        |
// +---------------------------------------------------------------------------+

func (o *Operations) lockItem(ctx context.Context, sess *db.Session, order *models.Order) (err error) {
	var list []*models.OrderItem

	// 1. 读取条目.
	if list, err = o.GetOrderItemListById(ctx, sess, order.Id); err != nil {
		return
	}

	// 2. 遍历条目.
	for _, item := range list {
		// 2.1 重复操作.
		if item.Locked.IsLocked() {
			continue
		}
		// 2.2 分发操作.
		if item.OrderKind.IsGoods() {
			// 冻结商品
			err = o.lockItemForGoods(ctx, sess, item)
		} else if item.OrderKind.IsSession() {
			// 冻结购票
			err = o.lockItemForSession(ctx, sess, item)
		}
		// 2.3 退出操作.
		if err != nil {
			break
		}
	}
	return
}

func (o *Operations) lockItemForGoods(ctx context.Context, sess *db.Session, item *models.OrderItem) (err error) {
	var affects, updates int64

	// 1. 开始冻结.
	log.Infofc(ctx, `[lib][order][lock] 冻结商品: 商品ID="%v", 商品名="%s", 冻结数量="%v"`, item.ResourceId, item.Name, item.Quantity)

	// 2. 监听结束.
	defer func() {
		if err != nil {
			log.Warnfc(ctx, `[lib][order][lock] 冻结商品失败: %v`, err)
		} else {
			log.Infofc(ctx, `[lib][order][lock] 冻结商品结束: 更新商品数="%v", 更新状态数="%v"`, affects, updates)
		}
	}()

	// 3. 冻结数量.
	if affects, err = sess.Incr("locked_count", item.Quantity).Where("id = ?", item.ResourceId).Update(models.Goods{}); err != nil {
		err = errs.ErrDatabaseQuery
		return
	}

	// 4. 更新状态.
	if updates, err = services.NewOrderItemService(sess).Dao.UpdateFieldsById(ctx, map[string]any{
		"locked": kind.LockedStateLocked,
	}, item.Id); err != nil {
		err = errs.ErrDatabaseQuery
	}
	return
}

func (o *Operations) lockItemForSession(ctx context.Context, sess *db.Session, item *models.OrderItem) (err error) {
	var affects, updates int64

	// 1. 开始冻结.
	log.Infofc(ctx, `[lib][order][lock] 冻结渔票: 渔票ID="%v", 渔票名="%s", 冻结数量="%v"`, item.ResourceId, item.Name, item.Quantity)

	// 2. 监听结束.
	defer func() {
		if err != nil {
			log.Warnfc(ctx, `[lib][order][lock] 冻结渔票失败: %v`, err)
		} else {
			log.Infofc(ctx, `[lib][order][lock] 冻结渔票结束: 更新渔票数="%v", 更新状态数="%v"`, affects, updates)
		}
	}()

	// 3. 冻结数量.
	if affects, err = sess.Incr("locked_count", item.Quantity).Where("id = ?", item.ResourceId).Update(models.TicketSession{}); err != nil {
		err = errs.ErrDatabaseQuery
		return
	}

	// 4. 更新状态.
	if updates, err = services.NewOrderItemService(sess).Dao.UpdateFieldsById(ctx, map[string]any{
		"locked": kind.LockedStateLocked,
	}, item.Id); err != nil {
		err = errs.ErrDatabaseQuery
	}
	return
}

func (o *Operations) lockPayKind(ctx context.Context, sess *db.Session, order *models.Order) (err error) {
	var list []*models.OrderPayKind

	// 1. 读取支付.
	if list, err = o.GetOrderPayKindListById(ctx, sess, order.Id); err != nil {
		return
	}

	// 2. 遍历支付.
	for _, item := range list {
		// 2.1 重复操作.
		if item.Locked.IsLocked() {
			continue
		}
		// 2.2 分发操作.
		if item.PayKind.IsCoupon() {
			// 冻结优惠券.
			err = o.lockPayKindForCoupon(ctx, sess, order, item)
		} else if item.PayKind.IsUserBalance() {
			// 冻结余额.
			err = o.lockPayKindForUserBalance(ctx, sess, order, item)
		}
		// 2.3 退出操作.
		if err != nil {
			break
		}
	}
	return
}

func (o *Operations) lockPayKindForCoupon(ctx context.Context, sess *db.Session, order *models.Order, item *models.OrderPayKind) (err error) {
	var affects, updates int64

	// 1. 开始冻结.
	log.Infofc(ctx, `[lib][order][lock] 冻结优惠券: 优惠券ID="%v", 优惠金额="%s"`, item.ResourceId, item.Amount)

	// 2. 监听结束.
	defer func() {
		if err != nil {
			log.Warnfc(ctx, `[lib][order][lock] 冻结优惠券失败: %v`, err)
		} else {
			log.Infofc(ctx, `[lib][order][lock] 冻结优惠券结束: 更新优惠券数="%v", 更新状态数="%v"`, affects, updates)
		}
	}()

	// 3. 冻结数量.
	if affects, err = services.NewCouponService(sess).Dao.UpdateFieldsById(ctx, map[string]any{
		"state":       kind.CouponStateUsed,
		"order_no":    order.OrderNo,
		"used_amount": item.Amount,
	}, item.ResourceId); err != nil {
		err = errs.ErrDatabaseQuery
		return
	}

	// 4. 更新状态.
	if updates, err = services.NewOrderPayKindService(sess).Dao.UpdateFieldsById(ctx, map[string]any{
		"locked": kind.LockedStateLocked,
	}, item.Id); err != nil {
		err = errs.ErrDatabaseQuery
	}
	return
}

func (o *Operations) lockPayKindForUserBalance(ctx context.Context, sess *db.Session, order *models.Order, item *models.OrderPayKind) (err error) {
	var affects, updates int64

	// 1. 开始冻结.
	log.Infofc(ctx, `[lib][order][lock] 冻结余额: 用户ID="%v", 抵扣金额="%s"`, order.UserId, item.Amount)

	// 2. 监听结束.
	defer func() {
		if err != nil {
			log.Warnfc(ctx, `[lib][order][lock] 冻结余额失败: %v`, err)
		} else {
			log.Infofc(ctx, `[lib][order][lock] 冻结余额结束: 更新余额数="%v", 更新状态数="%v"`, affects, updates)
		}
	}()

	// 3. 冻结余额.
	if affects, err = user_balance.New().Lock(ctx, sess, order.UserId, item.Amount, kind.BalanceOperateLock, kind.LinkDetailOrder, map[string]any{
		"Id":      order.Id,
		"OrderNo": order.OrderNo,
	}); err != nil {
		return
	}

	// 4. 更新状态.
	if updates, err = services.NewOrderPayKindService(sess).Dao.UpdateFieldsById(ctx, map[string]any{
		"locked": kind.LockedStateLocked,
	}, item.Id); err != nil {
		err = errs.ErrDatabaseQuery
	}
	return
}

func (o *Operations) unlockItem(ctx context.Context, sess *db.Session, order *models.Order) (err error) {
	var list []*models.OrderItem

	// 1. 读取条目.
	if list, err = o.GetOrderItemListById(ctx, sess, order.Id); err != nil {
		return
	}

	// 2. 遍历条目.
	for _, item := range list {
		// 2.1 重复操作.
		if item.Locked.IsLocked() {
			// 2.2 分发操作.
			if item.OrderKind.IsGoods() {
				// 解冻商品
				err = o.unlockItemForGoods(ctx, sess, item)
			} else if item.OrderKind.IsSession() {
				// 解冻购票
				err = o.unlockItemForSession(ctx, sess, item)
			}
			// 2.3 退出操作.
			if err != nil {
				break
			}
		}
	}
	return
}

func (o *Operations) unlockItemForGoods(ctx context.Context, sess *db.Session, item *models.OrderItem) (err error) {
	var affects, updates int64

	// 1. 开始解冻.
	log.Infofc(ctx, `[lib][order][unlock] 解冻商品: 商品ID="%v", 商品名="%s", 解冻数量="%v"`, item.ResourceId, item.Name, item.Quantity)

	// 2. 监听结束.
	defer func() {
		if err != nil {
			log.Warnfc(ctx, `[lib][order][unlock] 解冻商品失败: %v`, err)
		} else {
			log.Infofc(ctx, `[lib][order][unlock] 解冻商品结束: 更新商品数="%v", 更新状态数="%v"`, affects, updates)
		}
	}()

	// 3. 解冻数量.
	if affects, err = sess.Decr("locked_count", item.Quantity).Where("id = ?", item.ResourceId).Update(models.Goods{}); err != nil {
		err = errs.ErrDatabaseQuery
		return
	}

	// 4. 更新状态.
	if updates, err = services.NewOrderItemService(sess).Dao.UpdateFieldsById(ctx, map[string]any{
		"locked": kind.LockedStateReleased,
	}, item.Id); err != nil {
		err = errs.ErrDatabaseQuery
	}
	return
}

func (o *Operations) unlockItemForSession(ctx context.Context, sess *db.Session, item *models.OrderItem) (err error) {
	var affects, updates int64

	// 1. 开始解冻.
	log.Infofc(ctx, `[lib][order][unlock] 解冻渔票: 渔票ID="%v", 渔票名="%s", 解冻数量="%v"`, item.ResourceId, item.Name, item.Quantity)

	// 2. 监听结束.
	defer func() {
		if err != nil {
			log.Warnfc(ctx, `[lib][order][unlock] 解冻渔票失败: %v`, err)
		} else {
			log.Infofc(ctx, `[lib][order][unlock] 解冻渔票结束: 更新渔票数="%v", 更新状态数="%v"`, affects, updates)
		}
	}()

	// 3. 解冻数量.
	if affects, err = sess.Decr("locked_count", item.Quantity).Where("id = ?", item.ResourceId).Update(models.TicketSession{}); err != nil {
		err = errs.ErrDatabaseQuery
		return
	}

	// 4. 更新状态.
	if updates, err = services.NewOrderItemService(sess).Dao.UpdateFieldsById(ctx, map[string]any{
		"locked": kind.LockedStateReleased,
	}, item.Id); err != nil {
		err = errs.ErrDatabaseQuery
	}
	return
}

func (o *Operations) unlockPayKind(ctx context.Context, sess *db.Session, order *models.Order) (err error) {
	var list []*models.OrderPayKind

	// 1. 读取支付.
	if list, err = o.GetOrderPayKindListById(ctx, sess, order.Id); err != nil {
		return
	}

	// 2. 遍历支付.
	for _, item := range list {
		// 2.1 重复操作.
		if item.Locked.IsLocked() {
			// 2.2 分发操作.
			if item.PayKind.IsCoupon() {
				// 解冻优惠券.
				err = o.unlockPayKindForCoupon(ctx, sess, order, item)
			} else if item.PayKind.IsUserBalance() {
				// 解冻余额.
				err = o.unlockPayKindForUserBalance(ctx, sess, order, item)
			}
			// 2.3 退出操作.
			if err != nil {
				break
			}
		}
	}
	return
}

func (o *Operations) unlockPayKindForCoupon(ctx context.Context, sess *db.Session, order *models.Order, item *models.OrderPayKind) (err error) {
	var affects, updates int64

	// 1. 开始解冻.
	log.Infofc(ctx, `[lib][order][unlock] 解冻优惠券: 优惠券ID="%v", 优惠金额="%s"`, item.ResourceId, item.Amount)

	// 2. 监听结束.
	defer func() {
		if err != nil {
			log.Warnfc(ctx, `[lib][order][unlock] 解冻优惠券失败: %v`, err)
		} else {
			log.Infofc(ctx, `[lib][order][unlock] 解冻优惠券结束: 更新优惠券数="%v", 更新状态数="%v"`, affects, updates)
		}
	}()

	// 3. 解冻数量.
	if affects, err = services.NewCouponService(sess).Dao.UpdateFieldsById(ctx, map[string]any{
		"state":       kind.CouponStateVisible,
		"order_no":    "",
		"used_amount": 0,
	}, item.ResourceId); err != nil {
		err = errs.ErrDatabaseQuery
		return
	}

	// 4. 更新状态.
	if updates, err = services.NewOrderPayKindService(sess).Dao.UpdateFieldsById(ctx, map[string]any{
		"locked": kind.LockedStateReleased,
	}, item.Id); err != nil {
		err = errs.ErrDatabaseQuery
	}
	return
}

func (o *Operations) unlockPayKindForUserBalance(ctx context.Context, sess *db.Session, order *models.Order, item *models.OrderPayKind) (err error) {
	var affects, updates int64

	// 1. 开始解冻.
	log.Infofc(ctx, `[lib][order][unlock] 解冻余额: 用户ID="%v", 抵扣金额="%s"`, order.UserId, item.Amount)

	// 2. 监听结束.
	defer func() {
		if err != nil {
			log.Warnfc(ctx, `[lib][order][unlock] 解冻余额失败: %v`, err)
		} else {
			log.Infofc(ctx, `[lib][order][unlock] 解冻余额结束: 更新余额数="%v", 更新状态数="%v"`, affects, updates)
		}
	}()

	// 3. 解冻余额.
	if err = user_balance.New().Unlock(ctx, sess, order.UserId, item.Amount, kind.BalanceOperateUnlock, kind.LinkDetailOrder, map[string]any{
		"Id":      order.Id,
		"OrderNo": order.OrderNo,
	}); err != nil {
		return
	}

	// 4. 更新状态.
	if updates, err = services.NewOrderPayKindService(sess).Dao.UpdateFieldsById(ctx, map[string]any{
		"locked": kind.LockedStateReleased,
	}, item.Id); err != nil {
		err = errs.ErrDatabaseQuery
	}
	return
}

func (o *Operations) updateState(ctx context.Context, sess *db.Session, order *models.Order, state kind.OrderState, op kind.OrderOperation, by kind.ClosedBy, link kind.Link, params kind.LinkParams) (err error) {
	// 1. 开始更新.
	log.Infofc(ctx, `[lib][order][state] 开始更新状态: 订单ID="%v", 订单号="%s", 当前状态="%s", 目标状态="%s"`, order.Id, order.OrderNo, order.State.Text(), state.Text())

	// 2. 监听结束.
	defer func() {
		if err != nil {
			log.Warnfc(ctx, `[lib][order][state] 更新状态失败: %v`, err)
		} else {
			log.Infofc(ctx, `[lib][order][state] 更新状态结束`)
		}
	}()

	// 3. 重复关闭
	if order.State.IsClosed() {
		err = errs.ErrOrderStateClosedAlready
		return
	}

	// 4. 不可关闭.
	if !order.CanClose() {
		err = errs.ErrOrderStateNotAllowClose
		return
	}

	// 5. 主表状态.
	fields := map[string]any{"state": state}

	// 5.1 关闭人.
	if state.IsClosed() {
		fields["closed_by"] = by
	}

	// 5.2 更新状态.
	if _, err = services.NewOrderService(sess).Dao.UpdateFieldsById(ctx, fields, order.Id); err != nil {
		err = errs.ErrDatabaseQuery
		return
	}

	// 6. 卖家状态.
	if _, err = services.NewOrderArcadeService(sess).Dao.UpdateFieldsByMap(ctx, map[string]any{
		"state": state,
	}, map[string]any{
		"order_id": order.Id,
	}); err != nil {
		err = errs.ErrDatabaseQuery
		return
	}

	// 7. 买家状态.
	if _, err = services.NewOrderUserService(sess).Dao.UpdateFieldsByMap(ctx, map[string]any{
		"state": state,
	}, map[string]any{
		"order_id": order.Id,
	}); err != nil {
		err = errs.ErrDatabaseQuery
		return
	}

	// 8. 写入日志.
	if _, err = services.NewOrderLogService(sess).Dao.AddByStruct(ctx, &models.OrderLog{
		OrderId:    order.Id,
		Operation:  op,
		Link:       link,
		LinkParams: params,
		Message:    op.ToMessage(),
	}); err != nil {
		err = errs.ErrDatabaseQuery
	}
	return
}
